﻿#region copyright
//                Copyright Andrew Rafas 2012.
// Distributed under the Eclipse Public License, Version 1.0.
//           (See accompanying file LICENSE.txt or 
//     copy at http://www.eclipse.org/legal/epl-v10.html)
#endregion
using System;
using System.Collections.Generic;

namespace CSFP.Memoize
{
    public abstract class Dependent : IDependent, ISource
    {
        protected int _notReadyCount; // if == 0 then we can calculate this dependent, otherwise the value will be invalid
        protected bool _isCached; // can be false even if _uncachedCount == 0
        protected DependentList _dependentList;
#if DEBUG
        internal readonly ISource[] SourceList;
#endif

        protected Dependent(params ISource[] sourceList)
        {
#if DEBUG
            SourceList = sourceList;
#endif
            _notReadyCount = 0;
            _isCached = false;
            _dependentList = EmptyDependentList.Instance;

            for (int i = 0; i < sourceList.Length; ++i) {
                var d = sourceList[i];
                d.AddDependent(this, i);
                if (!d.IsReady)
                    ++_notReadyCount;
            }
        }

        void IDependent.Notify(int readyChange, int key)
        {
#if DEBUG
            Helper.Log("Notify({0}): {1} ({2} -> {3}, {4})", ++Helper.NotificationCount, this, _notReadyCount, _notReadyCount - readyChange, key);
#endif
            var oldReady = _notReadyCount == 0;
            _notReadyCount -= readyChange;
            var newReady = _notReadyCount == 0;
            // if !_isCached then all dependencies, and all their dependencies, and so forth have !IsCached...
            // if oldReady == newReady then we do not have to do antyhing to keep this invariant...
            if (oldReady != newReady) {
                _isCached = false; // ready change implies invalidation
                _dependentList.NotifyAll(newReady ? DependentList.BecomeReady : DependentList.BecomeNotReady);
            } else if (_isCached) {
                _isCached = false;
                _dependentList.NotifyAll(DependentList.ValueChanged);
            }
        }

        bool ISource.IsReady
        {
            get { return _notReadyCount == 0; }
        }

        void ISource.AddDependent(IDependent dependency, int key)
        {
            _dependentList = _dependentList.Add(new DepKey(dependency, key));
        }
    }

    public abstract class FunctionBase<R> : Dependent, IValue<R>
    {
        protected R _cachedValue;

        protected FunctionBase(params ISource[] sourceList)
            : base(sourceList)
        {
        }

#if DEBUG
        [System.Diagnostics.Conditional("DEBUG")]
        void LogCall()
        {
            var sb = new System.Text.StringBuilder();
            for (int i = 0; i < SourceList.Length; ++i) {
                if (i != 0)
                    sb.Append(", ");
                object value = "?";
                sb.Append(SourceList[i].ToString());
            }
            Helper.Log("Calculate({0}): {1} ({2})", ++Helper.CalculationCount, this, sb.ToString());
        }
#endif

        public override string ToString()
        {
            return base._isCached ? _cachedValue.ToString() : "?";
        }

        protected abstract void UpdateCached();

        public R Value
        {
            get
            {
                if (base._isCached)
                    return _cachedValue;
                // We do not check IsReady, if the caller was so dumb to call us when we are not ready, 
                // then we will get an exception higher up in the function call chain.
#if DEBUG
                LogCall();
#endif
                UpdateCached();
                base._isCached = true;
#if DEBUG
                Helper.Log("Result: {0} := {1}", this, _cachedValue);
#endif
                // The prior call will cause ValueChanged notifications if some value gets cached 
                // higher up in the function call chain, but since we are most likely called from a 
                // function below us, the notification will not propagate down as they are most 
                // likely !_isCached. (It can propagate though if we fan out downwards...)
                base._dependentList.NotifyAll(DependentList.ValueChanged);
                return _cachedValue;
            }
        }
    }

    public sealed class Function<T, R> : FunctionBase<R>
    {
        IValue<T> _param;
        Func<T, R> _func;

        public Function(IValue<T> param, Func<T, R> pureFunction)
            : base(param)
        {
            _param = param;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param.Value);
        }
    }

    public sealed class Function<T1, T2, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        Func<T1, T2, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, Func<T1, T2, R> pureFunction)
            : base(param1, param2)
        {
            _param1 = param1;
            _param2 = param2;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value);
        }
    }

    public sealed class Function<T1, T2, T3, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        IValue<T3> _param3;
        Func<T1, T2, T3, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, IValue<T3> param3, Func<T1, T2, T3, R> pureFunction)
            : base(param1, param2, param3)
        {
            _param1 = param1;
            _param2 = param2;
            _param3 = param3;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value, _param3.Value);
        }
    }

    public sealed class Function<T1, T2, T3, T4, R> : FunctionBase<R>
    {
        IValue<T1> _param1;
        IValue<T2> _param2;
        IValue<T3> _param3;
        IValue<T4> _param4;
        Func<T1, T2, T3, T4, R> _func;

        public Function(IValue<T1> param1, IValue<T2> param2, IValue<T3> param3, IValue<T4> param4, Func<T1, T2, T3, T4, R> pureFunction)
            : base(param1, param2, param3, param4)
        {
            _param1 = param1;
            _param2 = param2;
            _param3 = param3;
            _param4 = param4;
            _func = pureFunction;
        }

        protected override void UpdateCached()
        {
            base._cachedValue = _func(_param1.Value, _param2.Value, _param3.Value, _param4.Value);
        }
    }
}
